﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Schema;
using System.Collections;
using System.IO;
using WSDL = System.Web.Services.Description;
using System.Data.SqlClient;
using System.Globalization;
using System.Data;
using DTO = Microsoft.MSE.Catalog.Contracts;
using Bus = Microsoft.MSE.Catalog.Module.Business;
using DataAccess = Microsoft.MSE.Catalog.Module.Data;

namespace Microsoft.MSE.Catalog.Module
{
    public class CatalogSvcHelper
    {
        public static string policyModelXml = @"<PolicyModel xmlns=""http://microsoft.com/mse/2007/runtime/policyModel"" 
                                        xmlns:mse=""http://services.microsoft.com/MSE"" 
                                        xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml"" >
                                        </PolicyModel>";


        #region Miscellanious helper functions
        
        public static bool IsGuidNullOrEmpty(string id)
        {
            if (string.IsNullOrEmpty(id))
                return true;
            try
            {
                Guid guidId = new Guid(id);
                if (Guid.Empty == guidId)
                    return true;
            }
            catch { return true; }
            return false;
        }

        public static string GetExceptionDetails(Exception ex)
        {
            return TraceHelper.GetExceptionDetails(ex);
        }

        public static string RemoveUriScheme(string endPointUri)
        {
            if (endPointUri.IndexOf("://") > -1)
            {
                endPointUri = endPointUri.Substring(endPointUri.IndexOf("://") + 3);
            }
            return endPointUri;
        }

        public static string VerifyBinding(XmlElement bindingXml)
        {
            string scheme = null;
            System.ServiceModel.Channels.Binding bnd = BindingHelper.DeserializeBinding(bindingXml);
            if (bnd != null)
            {
                scheme = bnd.Scheme;
            }
            else
            {
                throw new ApplicationException("Invalid binding format. Failed deserialize binding.");
            }
            return scheme;
        }

        public static DTO.MSEBinding AssembleBindingFromBindingElements(DTO.MSEBinding binding)
        {

            string bindingBaseNode = string.Empty;

            if (binding.BindingElements == null) return null; //Binding not found

            XmlDocument xDoc = new XmlDocument();
            xDoc.LoadXml(@"<bindings xmlns=""""></bindings>");
            XmlNode destinationNode = xDoc.SelectSingleNode("bindings");

            foreach (DTO.MSEBindingElement bele in binding.BindingElements) // There will be only one base binding type per binding
            {
                if (bele.ElementType == DTO.BindingElementType.BaseBinding)
                {
                    destinationNode.AppendChild(xDoc.ImportNode(bele.BindingElementConfig as XmlNode, true));
                    bindingBaseNode = bele.BindingElementConfig.Name;
                    bindingBaseNode = bindingBaseNode.Substring(bindingBaseNode.IndexOf(":") + 1);
                    break;
                }
            }

            destinationNode = destinationNode.FirstChild;
            //destinationNode = xDoc.SelectSingleNode("bindings/" + bindingBaseNode);
            XmlNode bindingNode = null;
            //Add all binding elements to binding
            foreach (DTO.MSEBindingElement bele in binding.BindingElements)
            {
                if (bele.ElementType == DTO.BindingElementType.Binding)
                {
                    bindingNode = destinationNode.AppendChild(xDoc.ImportNode(bele.BindingElementConfig as XmlNode, true));
                }

                if ((bindingNode != null) && (bele.ElementType == DTO.BindingElementType.BindingElement))
                {
                    bindingNode.AppendChild(xDoc.ImportNode(bele.BindingElementConfig as XmlNode, true));
                }
            }

            VerifyBinding(destinationNode.OwnerDocument.DocumentElement); //Make sure that binding is valid

            binding.BindingConfig = destinationNode.OwnerDocument.DocumentElement;

            return binding;
        }

        //This is used by the Resource \ Channel to aggregate policies applied at System Instance, System and Resource 
        public static DTO.Policy AssemblePolicyFromPolicyAssertion(Guid resourcePolicyID, DTO.Resource resource, DTO.Policy opVersionPolicy, SqlTransaction txn, SqlConnection conn)
        {
            //Get all policies for the resource
            List<Guid> policyList = new List<Guid>();
            DTO.Policy policy = new DTO.Policy();

            if (opVersionPolicy != null)
                if (opVersionPolicy.ID != Guid.Empty)
                    policyList.Add(opVersionPolicy.ID); //Operation Version policy

            if (resourcePolicyID != Guid.Empty) policyList.Add(resourcePolicyID); //Resource policy

            DTO.SystemInstance systemInstance = DataAccess.MSESystemInstance.GetSystemInstance(resource.InstanceID, txn, conn);
            if (systemInstance.Policy != null && systemInstance.Policy.ID != Guid.Empty) policyList.Add(systemInstance.Policy.ID); //System Instance policy

            DTO.System system = DataAccess.MSESystem.GetSystem(systemInstance.SystemID, txn, conn);
            if (system.Policy != null && system.Policy.ID != Guid.Empty) policyList.Add(system.Policy.ID); //System policy

            //Start

            XmlDocument policyXML = new XmlDocument();
            policyXML.LoadXml(policyModelXml);
            XmlNode destinationNode = policyXML.FirstChild as XmlNode;

            List<DTO.PolicyAssertion> assertions = new List<DTO.PolicyAssertion>();

            for (int i = policyList.Count ; i > 0; i--)
            {
                //Get list of associated assertions
                DTO.PolicyAssertionList assertionList = Bus.PolicyManager.ListPolicyAssertionsByPolicy(policyList[i-1], txn, conn);

                if (assertionList.PolicyAssertions != null)
                {
                    foreach (DTO.PolicyAssertionListElement assertionEle in assertionList.PolicyAssertions)
                    {
                        DTO.PolicyAssertion polAssertion = Bus.PolicyManager.GetPolicyAssertion(assertionEle.ID, txn, conn);
                        assertions.Add(polAssertion);
                        destinationNode.AppendChild(policyXML.ImportNode(polAssertion.Assertion as XmlNode, true));
                    }
                }
            }

            policy.PolicyAssertions = assertions;            
            policy.WSPolicy = policyXML.DocumentElement;

            //End
                       
            return policy;
        }

        public static XmlElement GetPolicyXml(List<DTO.PolicyAssertion> assertions)
        {
            if (assertions == null) return null;

            XmlDocument policyXML = new XmlDocument();
            policyXML.LoadXml(policyModelXml);
            XmlNode destinationNode = policyXML.FirstChild as XmlNode;

            foreach (DTO.PolicyAssertion assertion in assertions)
            {
                destinationNode.AppendChild(policyXML.ImportNode(assertion.Assertion as XmlNode, true));
            }

            return policyXML.DocumentElement;
        }

        public static DTO.Policy AssemblePolicyFromPolicyAssertion(Guid policyID, SqlTransaction txn, SqlConnection conn)
        {

            DTO.Policy policy = DataAccess.Policy.GetPolicy(policyID, txn, conn);
            if (policy.ID == Guid.Empty) return null;


                //Get list of associated assertions
                DTO.PolicyAssertionList assertionList = Bus.PolicyManager.ListPolicyAssertionsByPolicy(policy.ID, txn, conn);

                List<DTO.PolicyAssertion> assertions = new List<DTO.PolicyAssertion>();

                if (assertionList.PolicyAssertions != null)
                {
                    XmlDocument policyXML = new XmlDocument();
                    policyXML.LoadXml(policyModelXml);
                    XmlNode destinationNode = policyXML.FirstChild as XmlNode;

                    foreach (DTO.PolicyAssertionListElement assertionEle in assertionList.PolicyAssertions)
                    {
                        DTO.PolicyAssertion polAssertion = Bus.PolicyManager.GetPolicyAssertion(assertionEle.ID, txn, conn);
                        assertions.Add(polAssertion);
                        destinationNode.AppendChild(policyXML.ImportNode(polAssertion.Assertion as XmlNode, true));
                    }

                    policy.WSPolicy = policyXML.DocumentElement;
                }

                policy.PolicyAssertions = assertions;

            return policy;
        }

        #endregion

        #region Schema related helper functions

        public static XmlDocument ConverXmlSchemaToDOM(XmlSchema xmlSchema)
        {
            XmlDocument xmlDocument = new XmlDocument();
            xmlDocument.LoadXml(SerializeSchema(xmlSchema));
            return xmlDocument;
        }

        public static string ConvertWSDLToString(WSDL.ServiceDescription wsdl)
        {
            StringBuilder metadataXml = new StringBuilder();
            XmlWriterSettings xws = new XmlWriterSettings();
            xws.Indent = true;

            XmlWriter xw = XmlWriter.Create(metadataXml);
            wsdl.Write(xw);
            xw.Flush();
            return metadataXml.ToString();
        }

        public static XmlSchema LoadSchemaFromString(string schemaXml)
        {
            XmlSchema xs = null;
            StringBuilder sbErrors = new StringBuilder();
            sbErrors.Append("");
            ValidationEventHandler vh = new ValidationEventHandler
            (
                delegate(object ob, ValidationEventArgs args)
                {
                    try
                    {
                        sbErrors.AppendFormat("Schema Validation Error: Exception[{0}] Message[{1}]", GetExceptionDetails(args.Exception), args.Message);
                    }
                    catch (Exception ex)
                    {
                        sbErrors.AppendFormat("Failed to Validate Schema due to error [{0}]", GetExceptionDetails(ex));
                    }
                }
            );

            xs = XmlSchema.Read(new StringReader(schemaXml), vh);
            if (sbErrors.ToString().Length > 0)
            {
                xs = null;
            }
            return xs;
        }

        public static XmlSchema GetSchemaFromMap(Dictionary<string, XmlSchema> schemaHash, string schemaNamepsace, SqlTransaction txn, SqlConnection conn)
        {
            XmlSchema xs = null;
            if (schemaHash.ContainsKey(schemaNamepsace) == false)
            {
                DTO.Schema schema = Data.Schema.GetSchemaByNamespace(schemaNamepsace, txn, conn);
                xs = new XmlSchema();
                xs.TargetNamespace = schemaNamepsace;
                xs.AttributeFormDefault = schema.AttributeFormDefault;
                xs.ElementFormDefault = schema.ElementFormDefault;
                schemaHash.Add(schemaNamepsace, xs);
            }
            else
            {
                xs = schemaHash[schemaNamepsace];
            }
            return xs;
        }

        public static XmlDocument FixSchemaSelfReferences(XmlDocument schema, string prefix, string ns)
        {
            //TODO: Re engineer this for more robust solution

            string oldNS = string.Empty;
            XmlNamespaceManager nsmgr = new XmlNamespaceManager(schema.NameTable);
            nsmgr.AddNamespace(SC.XsdPrefix, SC.XsdNS);
            XmlNode nsAttrInSchema = schema.SelectSingleNode("/xsd:schema/@targetNamespace", nsmgr);
            List<string> oldPrefixes = new List<string>();
            List<XmlAttribute> oldAttrs = new List<XmlAttribute>();

            if (nsAttrInSchema != null) oldNS = nsAttrInSchema.Value;
            if (!oldNS.Equals(ns))
            { // Schema has been "renamed" so let's fix this
                if (nsAttrInSchema == null)
                {
                    nsAttrInSchema = schema.CreateAttribute("targetNamespace");
                    schema.SelectSingleNode("/xsd:schema", nsmgr).Attributes.Append((XmlAttribute)nsAttrInSchema);
                }
                nsAttrInSchema.Value = ns;
                string newschema = schema.OuterXml;
                newschema.Replace(oldNS, ns);
                schema = new XmlDocument();
                schema.LoadXml(newschema);
                nsmgr = new XmlNamespaceManager(schema.NameTable);
                nsmgr.AddNamespace(SC.XsdPrefix, SC.XsdNS);
            }
            //            XmlDocument schemaElement = (XmlDocument)schema.SelectSingleNode("/xsd:schema", nsmgr);
            XmlDocument schemaElement = new XmlDocument();
            schemaElement.LoadXml(schema.SelectSingleNode("/xsd:schema", nsmgr).OuterXml);
            XmlAttribute xmlnsAttr = null;
            if (schemaElement.Attributes != null)
            {
                foreach (XmlAttribute attr in schemaElement.Attributes)
                {
                    if (attr.LocalName.Equals("xmlns"))
                    {
                        xmlnsAttr = attr;
                    }
                }
            }

            if (xmlnsAttr != null)
            { // Visual Studio has a fit if the Schema uses xmlns= to identify the types so fix this
                schemaElement.Attributes.Remove(xmlnsAttr);
                foreach (XmlNode attr in schema.SelectNodes("//@type"))
                {
                    if (!attr.Value.Contains(":"))
                    {
                        attr.Value = string.Format("{0}:{1}", prefix, attr.Value);
                    }
                }
            }

            return schema;
        }

        public static void AppendImportElementToSchema(XmlDocument schema, XmlNode nxl)
        {
            bool found = false;
            XmlNamespaceManager xmlns = new XmlNamespaceManager(schema.NameTable);
            xmlns.AddNamespace("xsd", XmlSchema.Namespace);
            XmlNodeList importNodes = schema.SelectNodes("//xsd:import", xmlns);
            foreach (XmlNode xn in importNodes)
            {
                string name1 = null;
                string name2 = null;
                if (xn.Attributes["namespace"] != null) name1 = xn.Attributes["namespace"].Value;
                if (nxl.Attributes["namespace"] != null) name2 = nxl.Attributes["namespace"].Value;
                if ((!string.IsNullOrEmpty(name1)) && (!string.IsNullOrEmpty(name2)) && (name1.Equals(name2) == true))
                {
                    found = true;
                }
            }
            if (found == false)
            {
                // import does not exist so just add
                schema.DocumentElement.InsertBefore(nxl, schema.DocumentElement.FirstChild);
            }
        }

        public static XmlSchema LoadXmlSchemaFromString(string schemaXml)
        {
            XmlSchema xs = null;
            StringBuilder sbErrors = new StringBuilder();
            sbErrors.Append("");
            ValidationEventHandler vh = new ValidationEventHandler
            (
                delegate(object ob, ValidationEventArgs args)
                {
                    try
                    {
                        sbErrors.AppendFormat("Schema Validation Error: Exception[{0}] Message[{1}]", GetExceptionDetails(args.Exception), args.Message);
                    }
                    catch (Exception ex)
                    {
                        sbErrors.AppendFormat("Failed to Validate Schema due to error [{0}]", GetExceptionDetails(ex));
                    }
                }
            );

            xs = XmlSchema.Read(new StringReader(schemaXml), vh);
            if (sbErrors.ToString().Length > 0)
            {
                xs = null;
            }
            return xs;
        }

        public static XmlSchema CreateXmlSchemaFromSchema(DTO.Schema schema, bool addImports)
        {
            try
            {
                XmlDocument schemaDoc = null;
                XmlSchemaSet xss = new XmlSchemaSet();
                XmlSchema xmlSchema = new XmlSchema();
                xmlSchema.TargetNamespace = schema.NamespaceURI;
                xmlSchema.ElementFormDefault = XmlSchemaForm.Qualified;

                if (addImports == true)
                {
                    if (schema.ImportedSchemas != null && schema.ImportedSchemas.Count > 0)
                    {
                        schemaDoc = ConverXmlSchemaToDOM(xmlSchema);
                        foreach (DTO.SchemaImport si in schema.ImportedSchemas)
                        {
                            XmlElement xeImport = schemaDoc.CreateElement(schemaDoc.DocumentElement.Prefix, "import", schemaDoc.DocumentElement.NamespaceURI);
                            XmlAttribute xaNamespace = xeImport.Attributes.Append(schemaDoc.CreateAttribute("namespace"));
                            xaNamespace.Value = si.NamespaceURI;
                            schemaDoc.DocumentElement.AppendChild(xeImport);
                        }
                        xmlSchema = LoadXmlSchemaFromString(schemaDoc.OuterXml);
                    }
                }
                if (schema.Objects != null && schema.Objects.Count > 0)
                {
                    foreach (DTO.SchemaObject so in schema.Objects)
                    {
                        XmlSchemaObject xso = DeSerializeSchemaObject(so.Xml.OuterXml, new XmlQualifiedName(so.Name, schema.NamespaceURI));
                        xmlSchema.Items.Add(xso);
                    }
                }
                else
                {
                    xmlSchema = LoadXmlSchemaFromString(schema.XSD.OuterXml);
                }

                return xmlSchema;
            }
            catch (Exception ex)
            {
                throw new ApplicationException(string.Format("An error occurred while building XmlSchema from MSE Schema [{0}]", schema.NamespaceURI), ex);
            }
        }

        public static string SerializeSchema(XmlSchema xmlSchema)
        {
            StringBuilder sb = new StringBuilder();
            XmlWriter xw = XmlWriter.Create(sb);
            xmlSchema.Write(xw);
            xw.Flush();
            return sb.ToString();
        }

        public static XmlSchemaObject DeSerializeSchemaObject(string schemaXml, XmlQualifiedName objectName)
        {
            XmlSchemaObject schemaObject = null;
            XmlSchema xmlSchema = new XmlSchema();
            xmlSchema = LoadSchemaFromString(schemaXml);
            schemaObject = xmlSchema.Items[0];
            return schemaObject;
        }

        public static bool XMLCompare(XmlElement inputNode, XmlElement existingNode)
        {
            bool areEqual = true;

            // Number of Child Nodes is different 
            if (inputNode.ChildNodes.Count != existingNode.ChildNodes.Count)
            {
                areEqual = false;
                return areEqual;
            }
            // Number of Attributes is different 
            if (inputNode.Attributes.Count != existingNode.Attributes.Count)
            {
                areEqual = false;
                return areEqual;
            }

            // Compare the inner text if nodes have no childres
            if (inputNode.ChildNodes.Count == 0 && existingNode.ChildNodes.Count == 0)
            {
                if (String.Compare(inputNode.InnerText, existingNode.InnerText, false) != 0)
                {
                    areEqual = false;
                    return areEqual;
                }
            }

            // Compare Attribuet contents
            if (inputNode.Attributes.Count > 0)
            {
                for (int counter = 0; counter < inputNode.Attributes.Count; counter++)
                {
                    XmlAttribute inputAttributeNode = inputNode.Attributes[counter];
                    if (existingNode.Attributes.GetNamedItem(inputAttributeNode.Name) != null)
                    {
                        // Attribute values do not match
                        XmlAttribute existingAttributeNode = (XmlAttribute)existingNode.Attributes.GetNamedItem(inputAttributeNode.Name);
                        if (string.Compare(inputAttributeNode.Value, existingAttributeNode.Value, false) != 0)
                        {
                            areEqual = false;
                            return areEqual;
                        }
                    }
                    else
                    {
                        // Attribute does not exist
                        areEqual = false;
                        return areEqual;
                    }
                }
            }

            // Compare Node contents
            if (inputNode.ChildNodes.Count > 0)
            {
                for (int counter = 0; counter < inputNode.ChildNodes.Count; counter++)
                {
                    XmlNode inputChildNode = inputNode.ChildNodes[counter];
                    if (existingNode.ChildNodes.Count > counter)
                    {
                        // Attribute values do not match
                        XmlNode existingChildNode = (XmlNode)existingNode.ChildNodes[counter];
                        if (inputChildNode is XmlElement && existingChildNode is XmlElement)
                        {
                            if (XMLCompare((XmlElement)inputChildNode, (XmlElement)existingChildNode) == false)
                            {
                                areEqual = false;
                                return areEqual;
                            }
                        }
                        else if (string.Compare(inputChildNode.GetType().FullName, existingChildNode.GetType().FullName, true) != 0)
                        {
                            areEqual = false;
                            return areEqual;
                        }
                    }
                    else
                    {
                        // Node does not exist
                        areEqual = false;
                        return areEqual;
                    }
                }
            }
            return true;
        }

        public static string GetSchemaObjectQName(DTO.SchemaObject schemaObject)
        {
            return string.Format("{0}:{1}", schemaObject.Name, schemaObject.NamespaceUri);
        }

        public static string CreateKey(XmlQualifiedName qName, DTO.SchemaObjectType metaType)
        {
            return CreateKey(qName.Name, qName.Namespace, metaType);
        }

        public static string CreateKey(string name, string namespaceUri, DTO.SchemaObjectType metaType)
        {
            return string.Format("{0}|{1}|{2}", name, namespaceUri, metaType.ToString());
        }
        #endregion

        #region SQL related helper functions

        public static SqlConnection GetConnection()
        {
            //return new SqlConnection(Configurator.svcConfig.DBConnString);
            string conn = Configurator.svcConfig.DBConnString;
            //string conn = @"Initial Catalog=Microsoft.MSE.Repository;Data Source=.;MultipleActiveResultSets=True;Integrated Security=SSPI";
            return new SqlConnection(conn);
        }

        public static SqlConnection GetConnection(string connStr)
        {
            return new SqlConnection(connStr);
        }

        public static SqlTransaction GetTransaction(SqlConnection conn, IsolationLevel isolation)
        {
            return conn.BeginTransaction(isolation);
        }
        #endregion
    }
}
